﻿#include "FITKGraphObjectConstraintRigidBody.h"

// VTK
#include <vtkPoints.h>
#include <vtkVertex.h>
#include <vtkCellArray.h>
#include <vtkDoubleArray.h>
#include <vtkPolyData.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkProperty.h>
#include <vtkGlyph3D.h>
#include <vtkDistanceToCamera.h>
#include <vtkMaskPoints.h>
#include <vtkRenderer.h>
#include <vtkCamera.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>
#include <vtkAppendFilter.h>
#include <vtkPointSource.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKPolygonSource.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaConstraintRigidBody.h"
#include "FITK_Interface/FITKInterfacePhysics/FITKBoundaryLocation.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAssInstanceTR.h"
#include "FITK_Interface/FITKInterfaceModel/FITKMeshSurfaceElement.h"
#include "FITK_Interface/FITKInterfaceModel/FITKMeshSurfaceNode.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

namespace Exchange
{
    FITKGraphObjectConstraintRigidBody::FITKGraphObjectConstraintRigidBody(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaConstraintRigidBody* rigidData)
        : FITKGraphObjectInteractionBase(caseData, rigidData)
    {
        if (!rigidData)
        {
            return;
        }

        // Initialize.
        init();

        // Generate the poly data for shape label.
        bool flag = generatePolyData(rigidData);
        if (!flag)
        {
            return;
        }

        // Generate tie actor.
        generateGraph();

        // Update the visibility.
        updateVisibility();
    }

    void FITKGraphObjectConstraintRigidBody::update(bool forceUpdate)
    {
        // Update poly data after the interactions's set or surface changed.
        Interface::FITKAbaConstraintRigidBody* rigidBody = dynamic_cast<Interface::FITKAbaConstraintRigidBody*>(_dataObj);
        if (!m_ugrid || !rigidBody)
        {
            return;
        }

        // Get the location id.
        Interface::FITKBoundaryLocation* body = rigidBody->getBody();
        Interface::FITKBoundaryLocation* pin = rigidBody->getPin();
        Interface::FITKBoundaryLocation* tie = rigidBody->getTie();
        Interface::FITKBoundaryLocation* anaSurf = rigidBody->getAnalyticalSurface();
        Interface::FITKBoundaryLocation* refNode = rigidBody->getReferencePoint();

        if (!refNode)
        {
            return;
        }

        // Check if the component exists.
        bool isComp1Valid = getComponentValid(m_locationCompId_1);
        bool isComp2Valid = getComponentValid(m_locationCompId_2);
        bool isComp3Valid = getComponentValid(m_locationCompId_3);
        bool isComp4Valid = getComponentValid(m_locationCompId_4);

        bool needUpdate = false;
        // Check if the locations changed.

        if (!isComp1Valid || !isComp2Valid || !isComp3Valid || !isComp4Valid)
        {
            needUpdate = true;
        }

        // At least one region must be valid.
        int locationCompId1 = -1;
        int locationCompId2 = -1;
        int locationCompId3 = -1;
        int locationCompId4 = -1;
        bool isValid = false;

        // Check the locations.
        // Get the set( set or ref-node ) or surface id.
        if (body)
        {
            isValid |= body->isValid();
            locationCompId1 = body->getComponentID();
        }

        if (pin)
        {
            isValid |= pin->isValid();
            locationCompId2 = pin->getComponentID();
        }

        if (tie)
        {
            isValid |= tie->isValid();
            locationCompId3 = tie->getComponentID();
        }

        if (anaSurf)
        {
            isValid |= anaSurf->isValid();
            locationCompId4 = anaSurf->getComponentID();
        }

        needUpdate |= (locationCompId1 != m_locationCompId_1 || locationCompId2 != m_locationCompId_2 ||
            locationCompId3 != m_locationCompId_3 || locationCompId4 != m_locationCompId_4 || forceUpdate);
        if (!needUpdate)
        {
            // Update the visibility only.
            updateVisibility();
            return;
        }

        clearData();

        bool flag = generatePolyData(rigidBody);
        if (!flag)
        {
            return;
        }

        // Update the visibility at last.
        updateVisibility();

        // Modify actors.
        updateActors();
    }

    bool FITKGraphObjectConstraintRigidBody::isRelatedTo(int dataObjId)
    {
        // Check if the id is belong to Interaction Base.
        bool isRelatedBase = this->Superclass::isRelatedTo(dataObjId);
        if (isRelatedBase)
        {
            return true;
        }

        // Check if the addin location is the same as the data.
        bool hasLocRelated = (m_locationCompId_3 == dataObjId || m_locationCompId_4 == dataObjId);
        if (hasLocRelated)
        {
            return true;
        }

        return false;
    }

    void FITKGraphObjectConstraintRigidBody::init()
    {
        this->Superclass::init();

//#ifdef QT_NO_DEBUG
        if (!m_simplifiedGlyph)
        {
            // Set the glyph size.
            m_glyphSize = 10.;

            // Create the data source.
            m_polygonSource = FITKPolygonSource::New();
            m_polygonSource->SetGeneratePolygon(false);
            m_polygonSource->SetGeneratePolyline(true);
            m_polygonSource->SetRadius(1);
            m_polygonSource->SetNumberOfSides(10);
        }
        else
        {
            // Set the glyph size.
            m_glyphSize = 15.;

            // Create the data source.
            m_pointSource = vtkPointSource::New();
            m_pointSource->SetRadius(0);
            m_pointSource->SetNumberOfPoints(1);
        }
//#endif // QT_NO_DEBUG
    }

    bool FITKGraphObjectConstraintRigidBody::generatePolyData(Interface::FITKInteractionLocation* constraint)
    {
        Interface::FITKAbaConstraintRigidBody* rigidBody = dynamic_cast<Interface::FITKAbaConstraintRigidBody*>(constraint);
        if (!rigidBody)
        {
            return false;
        }

        // Get the location id.
        Interface::FITKBoundaryLocation* body = rigidBody->getBody();
        Interface::FITKBoundaryLocation* pin = rigidBody->getPin();
        Interface::FITKBoundaryLocation* tie = rigidBody->getTie();
        Interface::FITKBoundaryLocation* anaSurf = rigidBody->getAnalyticalSurface();
        Interface::FITKBoundaryLocation* refNode = rigidBody->getReferencePoint();

        if (!refNode)
        {
            return false;
        }

        // Get the surface or set grid.
        QList<vtkUnstructuredGrid*> regionsGridList;

        // Lambda for creating locations' grid.
        auto addLocData = [&](Interface::FITKBoundaryLocation* loc)
        {
            QList<int> mIds;
            QList<int> subSetIds;
            vtkUnstructuredGrid* ugrid = vtkUnstructuredGrid::New();
            bool flag = generateLocPolyData(loc, mIds, subSetIds, ugrid);
            if (!flag)
            {
                ugrid->Delete();
            }
            else
            {
                regionsGridList.push_back(ugrid);
            }

            for (int & id : mIds)
            {
                if (!m_modelIds_1.contains(id))
                {
                    m_modelIds_1.push_back(id);
                }
            }

            for (int & id : subSetIds)
            {
                if (!m_subSetIds_1.contains(id))
                {
                    m_subSetIds_1.push_back(id);
                }
            }
        };

        // At least one region must be valid.
        bool isValid = false;
        if (body)
        {
            isValid |= body->isValid();
            m_locationCompId_1 = body->getComponentID();
            addLocData(body);
        }

        if (pin)
        {
            isValid |= pin->isValid();
            m_locationCompId_2 = pin->getComponentID();
            addLocData(pin);
        }

        if (tie)
        {
            isValid |= tie->isValid();
            m_locationCompId_3 = tie->getComponentID();
            addLocData(tie);
        }

        if (anaSurf)
        {
            isValid |= anaSurf->isValid();
            m_locationCompId_4 = anaSurf->getComponentID();
            addLocData(anaSurf);
        }

        if (!isValid)
        {
            return false;
        }

        // Get the appended grid data.
        vtkAppendFilter* appFilter = vtkAppendFilter::New();
        for (vtkUnstructuredGrid* ugrid : regionsGridList)
        {
            appFilter->AddInputData(ugrid);
        }

        appFilter->Update();

        // Generate with the cell centers.
        vtkDataSet* dataSet = appFilter->GetOutput();

        // Add a point and a cell.
        auto addPoint = [&](double* position, int modelId)
        {
            // Add point.
            int ptId = m_points->InsertNextPoint(position);

            // Add cell.
            vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            cell->InsertNextId(ptId);
            m_ugrid->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

            // Add model id.
            m_modelIdArray->InsertNextValue(modelId);
        };

        // Add regions points.
        //@{
        // Get the model id array of locations.
        vtkIntArray* modelIdArray = vtkIntArray::SafeDownCast(dataSet->GetCellData()->GetArray(FITKSetSurfPolyProvider::getInstance()->getModelIdsArrayName()));

        for (int i = 0; i < dataSet->GetNumberOfCells(); i++)
        {
            int modelId = -1;
            if (modelIdArray)
            {
                modelId = modelIdArray->GetValue(i);
            }

            double pt[3]{ 0., 0., 0. };
            FITKGraphCommons::GetCellCenter(dataSet->GetCell(i), pt);
            addPoint(pt, modelId);
        }
        //@}

        // Delete pointers.
        appFilter->Delete();
        for (vtkUnstructuredGrid* ugrid : regionsGridList)
        {
            ugrid->Delete();
        }

        regionsGridList.clear();

        return true;
    }

    void FITKGraphObjectConstraintRigidBody::generateGraph()
    {
        if (!m_ugrid || !m_extractSelection)
        {
            return;
        }

        // Initialize the actors.
        initActors();

        // Create the glyph filter.
        vtkGlyph3D* glyph = vtkGlyph3D::New();
        glyph->SetScaling(true);
        glyph->SetScaleModeToScaleByScalar();
        glyph->SetVectorModeToVectorRotationOff();

        // Add to data list.
        m_glyphList.push_back(glyph);

        m_fActor->setInputConnection(glyph->GetOutputPort());

//#ifdef QT_NO_DEBUG
        if (!m_simplifiedGlyph)
        {
            m_fActor->GetProperty()->SetLineWidth(1);

            m_extractSelection->SetInputData(m_ugrid);

            vtkDistanceToCamera* distanceToCamera = vtkDistanceToCamera::New();
            distanceToCamera->SetScreenSize(m_glyphSize);

            // Set the input.
            distanceToCamera->SetInputConnection(m_extractSelection->GetOutputPort());

            // Add the point array name need to be used.             
#if VTK_MAJOR_VERSION < 8
            glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
#else
            glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distanceToCamera->GetDistanceArrayName());
#endif

            // Set the input connection for glyph.
            if (m_useMaskPoints)
            {
                // Initialize the mask points.
                vtkMaskPoints* maskPoints = vtkMaskPoints::New();
                FITKGraphCommons::InitializeMaskPoints(maskPoints, distanceToCamera->GetOutputPort(), m_maxSampleNumber);
                m_maskPointsList.push_back(maskPoints);

                glyph->SetInputConnection(maskPoints->GetOutputPort());
            }
            else
            {
                glyph->SetInputConnection(distanceToCamera->GetOutputPort());
            }

            // Add to data list.
            m_distToCameraList.push_back(distanceToCamera);

            // Set the soure data and input data.
            glyph->SetSourceConnection(m_polygonSource->GetOutputPort());
            //@}
        }
        else
        {
            m_fActor->GetProperty()->SetPointSize(m_glyphSize);

            // Set the input connection for glyph.
            if (m_useMaskPoints)
            {
                // Initialize the mask points.
                vtkMaskPoints* maskPoints = vtkMaskPoints::New();
                FITKGraphCommons::InitializeMaskPoints(maskPoints, m_extractSelection->GetOutputPort(), m_maxSampleNumber);
                m_maskPointsList.push_back(maskPoints);

                glyph->SetInputConnection(maskPoints->GetOutputPort());
            }
            else
            {
                glyph->SetInputConnection(m_extractSelection->GetOutputPort());
            }   

            // Set the soure data and input connection.
            glyph->SetSourceConnection(m_pointSource->GetOutputPort());
        }
//#endif // QT_NO_DEBUG
    }

    void FITKGraphObjectConstraintRigidBody::clearData()
    {
        // Clear the basic data.
        this->Superclass::clearData();

        // Reset the additional component ids.
        m_locationCompId_3 = -1;
        m_locationCompId_4 = -1;
    }
}   // namespace Exchange